package tool.analysis;

import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;

import tool.csv.CSVCompatible;
import tool.csv.FileWriter;
import tool.data.AbstractClassEntity;
import tool.data.ConcreteClassEntity;
import tool.data.InterfaceEntity;
import tool.data.ResultStore;
import tool.data.TypeDefinition;

	/**
	 * @author jwb09119
	 * @date 2013/11/12
	 * 
	 * This class represents meta-data extracted from the raw data gathered from a parsed file
	 * structure.  It works with a {@link ResultStore} as it's data source.
	 *
	 */

public class InterfaceUseAnalyser extends CSVCompatible implements IJavaEntityAnalyser {
	
	private ResultStore results;
	private Map<String, InterfaceRecord> interfaceCharacteristics;
	
	// Constructor
	public InterfaceUseAnalyser (FileWriter fw, ResultStore log) {
		super(fw);
		
		this.results = log;
		this.interfaceCharacteristics = new TreeMap<>();
		
		doAnalysis();
	}
	
	
	/**
	 * Runs analysis on the data provided in results.
	 */
	private void doAnalysis () {
		
		results.visitJavaEntities(this);
		
	}
	
	
	@Override
	public void visit(InterfaceEntity javaEntity) {
		
		String name = javaEntity.getName();
		registerNewLocalInterface(name);
		
		for (TypeDefinition interfaceUsed : javaEntity.getImplementedInterfaces()) {
			
			registerImplementation(name, interfaceUsed.getNameWithPath());	
			
		}
		
	}
	
	
	@Override
	public void visit(AbstractClassEntity javaEntity) {
		
		String name = javaEntity.getName();
		
		for (TypeDefinition interfaceUsed : javaEntity.getImplementedInterfaces()) {
			
			registerImplementation(name, interfaceUsed.getNameWithPath());
			
		}
		
	}
	
	
	@Override
	public void visit(ConcreteClassEntity javaEntity) {
		
		String name = javaEntity.getName();
		
		for (TypeDefinition interfaceUsed : javaEntity.getImplementedInterfaces()) {
			
			registerImplementation(name, interfaceUsed.getNameWithPath());
			
		}
		
	}


	/**
	 * @param localInterface - String - the name of the local interface
	 */
	private void registerNewLocalInterface(String localInterface) {
		
		if (interfaceCharacteristics.containsKey(localInterface)) {
			
			interfaceCharacteristics.get(localInterface).definedLocally = true;
			
		} else {
		
			interfaceCharacteristics.put(localInterface, new InterfaceRecord(true));
		
		}
		
	}
	
	
	private int getLocalInterfaceCount () {
		
		int reply = 0;
		
		for (Entry<String, InterfaceRecord> t : interfaceCharacteristics.entrySet()) {
			
			if (t.getValue().definedLocally) {
				
				reply++;
				
			}
			
		}
		
		return reply;
		
	}
	
	
	/**
	 * Records the use of an interface by another entity
	 * @param implementor - String - the entity implementing an interface
	 * @param interfaceImplemented - String - the interface being implemented
	 * 
	 * NB - application convention is to list the full package*.name for a java type
	 */
	private void registerImplementation(String implementor, String interfaceImplemented) {

		if (interfaceCharacteristics.containsKey(interfaceImplemented)) {
			
			interfaceCharacteristics.get(interfaceImplemented).implementors.add(implementor);
			
		} else {
			
			// Interface may or may not be defined locally
			interfaceCharacteristics.put(interfaceImplemented, new InterfaceRecord(implementor));
			
		}
		
	}
	
	
	/**
	 * Prints a short summary of this analysis.
	 */
	public void printSummary() {
		
		System.out.println("Interfaces analysed: " + interfaceCharacteristics.size());
		
		int localCount = getLocalInterfaceCount();
		int nonLocalCount = interfaceCharacteristics.size() - localCount;
		
		System.out.println("Definition Split: " + localCount + " local definitions vs. " + nonLocalCount + " not local");
		
	}
	
	
	/**
	 * Prints a detailed summary of the this analysis.
	 */
	public void printDetailedSummary () {
		
		printSummary();
		
		System.out.println();
		
		for (Entry<String, InterfaceRecord> entry : interfaceCharacteristics.entrySet()) {
		
			InterfaceRecord record = entry.getValue();
			
			System.out.println("\t" + " [" + record.toString() + "] " + entry.getKey());
			
				for (String imp : record.implementors) {
					
					System.out.println("\t\tImplementer: " + imp);
					
				}
			
		}
		
		
	}
	
	
	/**
	 * @author jwb09119
	 * @date 2013/11/12
	 * 
	 * A 'record card' for an interface, basically a struct.  Since the class is private, I don't
	 *  see any point in getters and setters as the variables are exposed to the host class anyway.
	 */
	private class InterfaceRecord {
		
		private List<String> implementors;
		private boolean definedLocally;
		
		// Non-static constructor blocks are executed in each constructor
		{
			implementors = new ArrayList<>();
			definedLocally = false;
		}
		
		
		private InterfaceRecord (Boolean isLocal) {
			
			definedLocally = isLocal;
			
		}
		
		
		private InterfaceRecord (String implementor) {
			
			implementors.add(implementor);
			
		}
		
		
		private InterfaceRecord (String implementor, Boolean isLocal) {
			
			implementors.add(implementor);
			definedLocally = isLocal;
			
		}
		
		@Override
		public String toString () {
			
			return (definedLocally ? "LOCAL" : "NOT LOCAL") + ", Implementing Classes: " + implementors.size();
			
		}
		
	}

	
	/**************************
	 * CSV Compatible Methods *
	 **************************/

	@Override
	protected String getExportHeader() {
		
		return "Interfaces defined locally, or extended by local types";
		
	}


	@Override
	protected void writeStateToCSV(FileWriter writer) {
		
		writer.writeComment("FullName, TypeName, PackageAddr, NoImplementers, DefinedInLocalSourceCode");
		
		String fullName;
		String type;
		String addr;
		String noImpl;
		String local;
		
		int dot;
		String key;
		
		for (Entry<String, InterfaceRecord> i : interfaceCharacteristics.entrySet()) {
			
			key = i.getKey();
			dot = key.lastIndexOf('.');
			
			fullName = i.getKey();
			type = key.substring(dot+1, key.length());
			addr = (dot != -1) ? key.substring(0, dot) : "NO_DOT."+key; // If there is no dot?
			noImpl = String.valueOf(i.getValue().implementors.size());
			local = i.getValue().definedLocally ? "LOCAL" : "NOTLOCAL";
			
			writer.writeComment(fullName, type, addr, noImpl, local);
			
		}
		
	}


	/**
	 * Gets a list of all interface names encountered by this visitor, i.e. all interfaces 
	 * implemented by another class in the visited code base
	 * @return Set<String> - unique representations of interface names
	 */
    public Set<String> getAllInterfaceNames () {

        Set<String> reply = new LinkedHashSet<>();
        
        for (String i : interfaceCharacteristics.keySet()) {
            
            reply.add(new String (i));
            
        }
        
        return reply;
    }

	
}
